/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/*
 * Wirzenius wrote this portably, Torvalds fucked it up :-)
 */

#include <lib/vsprintf.h>
#include <lib/stdtypes.h>
#include <lib/stddef.h>
#include <lib/string.h>

/* we use this so that we can do without the ctype library */
#define is_digit(c)	((c) >= '0' && (c) <= '9')

static int skip_atoi(const char **s)
{
	int i = 0;

	while (is_digit(**s)) {
		i = i * 10 + *((*s)++) - '0';
	}

	return i;
}

#define ZEROPAD	1		/* pad with zero */
#define SIGN	2		/* unsigned/signed long */
#define PLUS	4		/* show plus */
#define SPACE	8		/* space if plus */
#define LEFT	16		/* left justified */
#define SPECIAL	32		/* 0x */
#define SMALL	64		/* use 'abcdef' instead of 'ABCDEF' */

#define do_div(n,base) ({ \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0),"r" (base)); \
__res; })

static char *number(char *str, int num, int base, int size, int precision
                    , int type)
{
	char c, sign, tmp[36];
	const char *digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
	int i;

	if (type & SMALL) {
		digits = "0123456789abcdefghijklmnopqrstuvwxyz";
	}

	if (type & LEFT) {
		type &= ~ZEROPAD;
	}

	if (base < 2 || base > 36) {
		return 0;
	}

	c = (type & ZEROPAD) ? '0' : ' ' ;

	if (type & SIGN && num < 0) {
		sign = '-';
		num = -num;
	} else {
		sign = (type & PLUS) ? '+' : ((type & SPACE) ? ' ' : 0);
	}

	if (sign) {
		size--;
	}

	if (type & SPECIAL) {
		if (base == 16) {
			size -= 2;
		} else if (base == 8) {
			size--;
		}
	}

	i = 0;

	if (num == 0) {
		tmp[i++] = '0';
	} else while (num != 0) {
			tmp[i++] = digits[do_div(num, base)];
		}

	if (i > precision) {
		precision = i;
	}

	size -= precision;

	if (!(type & (ZEROPAD + LEFT)))
		while (size-- > 0) {
			*str++ = ' ';
		}

	if (sign) {
		*str++ = sign;
	}

	if (type & SPECIAL) {
		if (base == 8) {
			*str++ = '0';
		} else if (base == 16) {
			*str++ = '0';
			*str++ = digits[33];
		}
	}

	if (!(type & LEFT))
		while (size-- > 0) {
			*str++ = c;
		}

	while (i < precision--) {
		*str++ = '0';
	}

	while (i-- > 0) {
		*str++ = tmp[i];
	}

	while (size-- > 0) {
		*str++ = ' ';
	}

	return str;
}

int vsprintf(char *buf, const char *fmt, va_list args)
{
	int len;
	int i;
	char *str;
	char *s;
	int *ip;

	int flags;		/* flags to number() */

	int field_width;	/* width of output field */
	int precision;		/* min. # of digits for integers; max
				   number of chars for from string */
	//int qualifier;		/* 'h', 'l', or 'L' for integer fields */

	for (str = buf ; *fmt ; ++fmt) {
		if (*fmt != '%') {
			*str++ = *fmt;
			continue;
		}

		/* process flags */
		flags = 0;
repeat:
		++fmt;		/* this also skips first '%' */

		switch (*fmt) {
		case '-': flags |= LEFT; goto repeat;

		case '+': flags |= PLUS; goto repeat;

		case ' ': flags |= SPACE; goto repeat;

		case '#': flags |= SPECIAL; goto repeat;

		case '0': flags |= ZEROPAD; goto repeat;
		}

		/* get field width */
		field_width = -1;

		if (is_digit(*fmt)) {
			field_width = skip_atoi(&fmt);
		} else if (*fmt == '*') {
			/* it's the next argument */
			field_width = va_arg(args, int);

			if (field_width < 0) {
				field_width = -field_width;
				flags |= LEFT;
			}
		}

		/* get the precision */
		precision = -1;

		if (*fmt == '.') {
			++fmt;

			if (is_digit(*fmt)) {
				precision = skip_atoi(&fmt);
			} else if (*fmt == '*') {
				/* it's the next argument */
				precision = va_arg(args, int);
			}

			if (precision < 0) {
				precision = 0;
			}
		}

		/* get the conversion qualifier */
		//qualifier = -1;
		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
			//qualifier = *fmt;
			++fmt;
		}

		switch (*fmt) {
		case 'c':
			if (!(flags & LEFT))
				while (--field_width > 0) {
					*str++ = ' ';
				}

			*str++ = (unsigned char) va_arg(args, int);

			while (--field_width > 0) {
				*str++ = ' ';
			}

			break;

		case 's':
			s = va_arg(args, char *);
			//if (!s)
			//	s = "<NULL>";
			len = strlen(s);

			if (precision < 0) {
				precision = len;
			} else if (len > precision) {
				len = precision;
			}

			if (!(flags & LEFT))
				while (len < field_width--) {
					*str++ = ' ';
				}

			for (i = 0; i < len; ++i) {
				*str++ = *s++;
			}

			while (len < field_width--) {
				*str++ = ' ';
			}

			break;

		case 'o':
			str = number(str, va_arg(args, unsigned long), 8,
			             field_width, precision, flags);
			break;

		case 'p':
			if (field_width == -1) {
				field_width = 8;
				flags |= ZEROPAD;
			}

			str = number(str,
			             (unsigned long) va_arg(args, void *), 16,
			             field_width, precision, flags);
			break;

		case 'x':
			flags |= SMALL;

		case 'X':
			str = number(str, va_arg(args, unsigned long), 16,
			             field_width, precision, flags);
			break;

		case 'd':
		case 'i':
			flags |= SIGN;

		case 'u':
			str = number(str, va_arg(args, unsigned long), 10,
			             field_width, precision, flags);
			break;

		case 'b':
			str = number(str, va_arg(args, unsigned long), 2,
			             field_width, precision, flags);
			break;

		case 'n':
			ip = va_arg(args, int *);
			*ip = (str - buf);
			break;

		default:
			if (*fmt != '%') {
				*str++ = '%';
			}

			if (*fmt) {
				*str++ = *fmt;
			} else {
				--fmt;
			}

			break;
		}
	}

	*str = '\0';
	return str - buf;
}

int sprintf(char *buf, const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	i = vsprintf(buf, fmt, args);
	va_end(args);

	return i;
}
